﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Org.Alexander.Edas
{
    public class Acceptor
    {
        private Socket Listenner;
        private int BuffSize;
        private ConnectorPool Pool;
        private SocketAsyncEventArgs AcceptArg;
        private AutoResetEvent AcceptLock;
        private int Port;

        public event AcceptInfoHandler OnStart;
        public event AcceptInfoHandler OnStop;
        public event AcceptErrorHandler OnError;
        public event SessionDataHandler OnSessionStart;
        public event AcceptInfoHandler OnSessionEnd;
        public event SessionDataReadHandler OnReaded;
        public event SessionDataWriteHandler OnSended;

        public IProtocolFactory ProtocolFactory
        {
            get;
            set;
        }

        public Acceptor(int Bind) : this(Bind, 4096) { }
        public Acceptor(int Bind, int BufferSize) : this(Bind, BufferSize, 2000) { }
        public Acceptor(int Bind,int BufferSize,int Capital)
        {
            BuffSize = BufferSize;
            Listenner = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            Listenner.Bind(
                new IPEndPoint(IPAddress.Any,Bind)
                );
            Port = Bind;
            Pool = ConnectorPool.GetIns();
            Pool.Init(Capital, BufferSize,this);
            AcceptLock = new AutoResetEvent(true);
            AcceptArg = new SocketAsyncEventArgs();
            AcceptArg.UserToken = Listenner;
            AcceptArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptArg_Completed);
        }

        public void Start()
        {
            if (ProtocolFactory != null)
            {
                OnReaded += new SessionDataReadHandler(Acceptor_OnReaded);
                OnSended += new SessionDataWriteHandler(Acceptor_OnSended);
            }
            Listenner.Listen(200);
            if (!Listenner.AcceptAsync(AcceptArg))
            {
                ProcessAccept(AcceptArg);
            }
            if (OnStart != null)
            {
                OnStart("Acceptor start listen at port " + Port);
            }
        }
        public void Close()
        {
            Listenner.Close(1);
            if (OnStop != null)
            {
                OnStop("Acceptor stopped");
            }
        }

        void AcceptArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            //throw new NotImplementedException();
            AcceptLock.WaitOne();
            ProcessAccept(e);
            AcceptLock.Set();
        }

        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success)
                {
                    InitAcceptConnector(e);
                    
                    e.AcceptSocket = null;
                    if (!Listenner.AcceptAsync(e))
                    {
                        ProcessAccept(e);
                    }
                }
                else
                {
                    if (OnError != null)
                    {
                        OnError(e.SocketError.ToString(), null);
                    }
                }
            }
            catch (Exception ex)
            {
                if (OnError != null)
                {
                    OnError("Accept with error:", ex);
                }
            }
        }

        private void InitAcceptConnector(SocketAsyncEventArgs e)
        {
            Connector con = Pool.GetFreeConnector();
            con.Init(e.AcceptSocket);

            if (ProtocolFactory != null)
            {
                IProtocol proc = ProtocolFactory.GetProtocol();
                proc.IOBuffer = new MemoryStream();
                proc.Package = new MemoryStream();
                con.StateObject = proc;
            }
            if (OnSessionStart != null)
            {
                OnSessionStart(con);
            }
            con.WaitingRead();
        }

        void Acceptor_OnSended(Connector Sender)
        {
            //throw new NotImplementedException();
            IProtocol proc = (IProtocol)Sender.StateObject;
            if (proc.Status == ProcessStatus.HandShakeOk)
            {
                proc.Status = ProcessStatus.Header;
                ProcessHeader(Sender, proc);
            }
            if (proc.Status != ProcessStatus.HandShake&&proc.Status!=ProcessStatus.HandShakeOk)
            {
                if (proc.IsSendReply)
                {
                    ThreadPool.QueueUserWorkItem(
                        (WaitCallback)delegate(Object sender)
                    {
                        proc.OnReplyOk(Sender);
                        proc.IsSendReply = false;
                        if (!proc.KeepSending)
                        {
                            proc.Status = ProcessStatus.Header;
                            ProcessHeader(Sender, proc);
                        }
                    }
                    );
                }

            }
        }

        void Acceptor_OnReaded(Connector Sender,int ReadCount)
        {
            //throw new NotImplementedException();
            IProtocol proc = (IProtocol)Sender.StateObject;
            proc.IOBuffer.Write(Sender.Buffer, 0, ReadCount);
            if (proc.Status == ProcessStatus.HandShake)
            {
                ProcessHandShake(Sender, proc);
            }
            if (ProcessStatus.Header == proc.Status)
            {
                ProcessHeader(Sender, proc);
            }
            if (ProcessStatus.Body == proc.Status)
            {
                ProcessBody(Sender, proc);
            }

        }

        private static void ProcessBody(Connector Sender, IProtocol proc)
        {
            int len = proc.BodyLength;
            if (proc.IOBuffer.Length >= len)
            {
                byte[] temp = ReadFromIoBuffer(proc, len);
                proc.Package.Write(proc.Header, 0, proc.Header.Length);
                proc.Package.Write(temp, 0, temp.Length);   
                ThreadPool.QueueUserWorkItem(
                    (WaitCallback)delegate(object sender){
                        lock (Sender)
                        {
                            proc.OnGetBody(proc.Package.ToArray(), Sender);
                        }
                    }
                );
                SaveOutData(proc, len);
                //proc.Status = ProcessStatus.Header;
                //ProcessHeader(Sender, proc);
            }
            else
            {
                Sender.WaitingRead();
            }
        }

        private static void ProcessHeader(Connector Sender, IProtocol proc)
        {
            if (proc.Status==ProcessStatus.Header)
            {
                int len = 0;
                if (proc.DynamicHeader && proc.HeaderLength == 0)
                {
                    len = proc.InitLength;
                }
                else
                {
                    len = proc.HeaderLength;
                }
                if (proc.IOBuffer.Length >= len)
                {
                    proc.Header = ReadFromIoBuffer(proc, len);
                    proc.OnGetHeader(Sender);
                    SaveOutData(proc, len);
                    proc.Status = ProcessStatus.Body; ;
                    ProcessBody(Sender, proc);
                }
                else
                {
                    Sender.WaitingRead();
                }
            }
            else
            {
                Sender.WaitingRead();
            }
        }

        private static void ProcessHandShake(Connector Sender, IProtocol proc)
        {
            if (proc.Status == ProcessStatus.HandShake)
            {
                int len = proc.HandShakeLength[proc.HandShakeCount];
                if (proc.IOBuffer.Length >= len)
                {
                    proc.HandShakeData = ReadFromIoBuffer(proc, len);
                    proc.OnHandShake(Sender);
                    SaveOutData(proc, len);
                    if (proc.HandShakeCount == proc.HandShakeLength.Length - 1)
                    {
                        proc.Status = ProcessStatus.HandShakeOk;
                    }
                    else
                    {
                        proc.HandShakeCount++;
                        ProcessHandShake(Sender, proc);
                    }
                }
                else
                {
                    Sender.WaitingRead();
                }
            }
            else
            {
                Sender.WaitingRead();
            }
        }

        private static void SaveOutData(IProtocol proc, int len)
        {
            if (proc.IOBuffer.Length > len)
            {
                byte[] temp2 = proc.IOBuffer.ToArray();
                proc.IOBuffer.SetLength(0);
                proc.IOBuffer.Write(temp2, len, temp2.Length - len);
            } if (proc.IOBuffer.Length == len)
            {
                proc.IOBuffer.SetLength(0);
            }
            proc.IOBuffer.Position = 0;
        }

        private static byte[] ReadFromIoBuffer(IProtocol proc, int len)
        {
            byte[] temp = new byte[len];
            proc.IOBuffer.Position = 0;
            proc.IOBuffer.Read(temp, 0, len);
            return temp;
        }

        internal void con_OnError(Connector Sender, Exception Ex)
        {
            //throw new NotImplementedException();
            if (OnError != null)
            {
                OnError("Session:" + Sender.ID + " Exit with Exception:" + Ex.Message, Ex);
            }
        }

        internal void con_OnSend(Connector Sender)
        {
            if (OnSended != null)
            {
                OnSended(Sender);
            }
        }

        internal void con_OnRead(Connector Sender, int ReadCount)
        {
            if (OnReaded != null)
            {
                OnReaded(Sender, ReadCount);
            }
            
        }

        internal void con_OnInited(Connector Sender)
        {
            //throw new NotImplementedException();
            if (OnSessionStart != null)
            {
                OnSessionStart(Sender);
            }
        }

        internal void con_OnClose(string Message)
        {
            //throw new NotImplementedException();
            if (OnSessionEnd != null)
            {
                OnSessionEnd(Message);
            }
        }


    }
}
